package com.example.glideprogressiveloadimg.util;

import android.media.ExifInterface;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;

public class TiffUtil {

//    private static final Class<?> TAG = TiffUtil.class;
    private static final String TAG = TiffUtil.class.getSimpleName();

    public static final int TIFF_BYTE_ORDER_BIG_END = 0x4D4D002A;  //TIFF信息， 大头标记
    public static final int TIFF_BYTE_ORDER_LITTLE_END = 0x49492A00;  //TIFF信息， 小头标记

    public static final int TIFF_TAG_ORIENTATION = 0x0112;
    public static final int TIFF_TYPE_SHORT = 3;

    /**
     * Determines auto-rotate angle based on orientation information.
     *
     * @param orientation orientation information read from APP1 EXIF (TIFF) block.
     * @return orientation: 1/3/6/8 -> 0/180/90/270. Returns 0 for inverted orientations (2/4/5/7).
     */
    public static int getAutoRotateAngleFromOrientation(int orientation) {
        switch (orientation) {
            case ExifInterface.ORIENTATION_NORMAL:
            case ExifInterface.ORIENTATION_UNDEFINED:
                return 0;
            case ExifInterface.ORIENTATION_ROTATE_180:
                return 180;
            case ExifInterface.ORIENTATION_ROTATE_90:
                return 90;
            case ExifInterface.ORIENTATION_ROTATE_270:
                return 270;
        }
        return 0;
    }

    /**
     * Reads orientation information from TIFF data.
     * 从TIFF数据读取方向信息。
     * @param is the input stream of TIFF data
     * @param length length of the TIFF data TIFF数据块的长度
     * @return orientation information (1/3/6/8 on success, 0 if not found)
     */
    public static int readOrientationFromTIFF(InputStream is, int length) throws IOException {
        // read tiff header
        TiffHeader tiffHeader = new TiffHeader();
        // 将TIFF头读取到所提供TiffHeader 对象中
        length = readTiffHeader(is, length, tiffHeader);

        // move to the first IFD
        // offset is relative to the beginning of the TIFF data
        // and we already consumed the first 8 bytes of header
        int toSkip = tiffHeader.firstIfdOffset - 8;
        if (length == 0 || toSkip > length) {
            return 0;
        }
        is.skip(toSkip);
        length -= toSkip;

        // move to the entry with orientation tag
        //将给定的输入流定位到 TIFF_TAG_ORIENTATION 标记上
        length = moveToTiffEntryWithTag(is, length, tiffHeader.isLittleEndian, TIFF_TAG_ORIENTATION);

        // read orientation
        //从方向标记位置，读取方向信息
        return getOrientationFromTiffEntry(is, length, tiffHeader.isLittleEndian);
    }

    /** Structure that holds TIFF header. */
    private static class TiffHeader {
        boolean isLittleEndian; //小头大头模式
        int byteOrder;  //字节顺序
        int firstIfdOffset;
    }

    /**
     * Reads the TIFF header to the provided structure.
     * 将TIFF头读取到所提供TiffHeader 对象中
     *
     * @param is the input stream of TIFF data
     * @param length length of the TIFF data
     * @return remaining length of the data on success, 0 on failure
     * @throws IOException
     */
    private static int readTiffHeader(InputStream is, int length, TiffHeader tiffHeader)
            throws IOException {
        if (length <= 8) {
            return 0;
        }

        // read the byte order  字节顺序
        tiffHeader.byteOrder = StreamProcessor.readPackedInt(is, 4, false);
        length -= 4;
        if (tiffHeader.byteOrder != TIFF_BYTE_ORDER_LITTLE_END
                && tiffHeader.byteOrder != TIFF_BYTE_ORDER_BIG_END) {
            Log.e(TAG, "Invalid TIFF header");
            return 0;
        }
        tiffHeader.isLittleEndian = (tiffHeader.byteOrder == TIFF_BYTE_ORDER_LITTLE_END);

        // read the offset of the first IFD and check if it is reasonable
        tiffHeader.firstIfdOffset = StreamProcessor.readPackedInt(is, 4, tiffHeader.isLittleEndian);
        length -= 4;
        if (tiffHeader.firstIfdOffset < 8 || tiffHeader.firstIfdOffset - 8 > length) {
            Log.e(TAG, "Invalid offset");
            return 0;
        }

        return length;
    }

    /**
     * Positions the given input stream to the entry that has a specified tag. Tag will be consumed.
     * 将给定的输入流定位到具有指定标记的条目上。标签将被消耗。
     *
     * @param is the input stream of TIFF data positioned to the beginning of an IFD.
     * @param length length of the available data in the given input stream.
     * @param isLittleEndian whether the TIFF data is stored in little or big endian format
     * @param tagToFind tag to find
     * @return remaining length of the data on success, 0 on failure
     */
    private static int moveToTiffEntryWithTag(InputStream is,
                                              int length,
                                              boolean isLittleEndian,
                                              int tagToFind) throws IOException {
        if (length < 14) {
            return 0;
        }
        // read the number of entries and go through all of them
        // each IFD entry has length of 12 bytes and is composed of
        // {TAG [2], TYPE [2], COUNT [4], VALUE/OFFSET [4]}
        int numEntries = StreamProcessor.readPackedInt(is, 2, isLittleEndian);
        length -= 2;
        while (numEntries-- > 0 && length >= 12) {
            int tag = StreamProcessor.readPackedInt(is, 2, isLittleEndian);
            length -= 2;
            if (tag == tagToFind) {
                return length;
            }
            is.skip(10);
            length -= 10;
        }
        return 0;
    }

    /**
     * Reads the orientation information from the TIFF entry. It is assumed that the entry has a TIFF
     * orientation tag and that tag has already been consumed.
     * 从TIFF条目读取方向信息。假定条目有一个TIFF方向标记，并且该标记已经被使用。
     *
     * @param is the input stream positioned at the TIFF entry with tag already being consumed
     * @param isLittleEndian whether the TIFF data is stored in little or big endian format
     * @return Orientation value in TIFF IFD entry.
     */
    private static int getOrientationFromTiffEntry(InputStream is, int length, boolean isLittleEndian)
            throws IOException {
        if (length < 10) {
            return 0;
        }
        // orientation entry has type = short
        int type = StreamProcessor.readPackedInt(is, 2, isLittleEndian);
        if (type != TIFF_TYPE_SHORT) {
            return 0;
        }
        // orientation entry has count = 1
        int count = StreamProcessor.readPackedInt(is, 4, isLittleEndian);
        if (count != 1) {
            return 0;
        }
        int value = StreamProcessor.readPackedInt(is, 2, isLittleEndian);
        return value;
    }

}
